package cn.edu.hit.kg;

import cn.edu.hit.selflearning.NeoDriver;
import net.sf.json.JSONArray;
import org.apache.commons.math3.linear.MatrixUtils;
import org.apache.commons.math3.linear.RealVector;
import org.neo4j.driver.v1.Value;
import org.neo4j.driver.v1.Values;
import org.neo4j.driver.v1.types.Node;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class SelfLearningUtil {

    public static Map<Long, Integer> rowMap = new ConcurrentHashMap<>();
    public static Map<Long, Integer> columnMap = new ConcurrentHashMap<>();
    public static Map<Long, RealVector> trainMap = new ConcurrentHashMap<>();

    public static JSONArray doInit(long id, Map<Long, Integer> rowMap, Map<Long, Integer> columnMap, Map<Long, RealVector> trainMap) {
        try (NeoDriver driver = new NeoDriver()) {
            return driver.execute(session -> {
                Optional<Node> nodeOptional = session.run("match (n:Fault) where id(n)=$id return n", Values.parameters("id", id))
                        .list(x -> x.get("n")).stream().map(Value::asNode).findFirst();
                if (nodeOptional.isPresent()) {
                    Node f = nodeOptional.get();
                    List<Double> r_matrix = f.get("matrix").asList(x -> x.asDouble());
                    int fn = r_matrix.get(0).intValue();
                    int rn = r_matrix.get(1).intValue();
                    double[] rowRequire = new double[fn];
                    double[] columnRequire = new double[rn];
                    double[][] matrix = new double[fn][rn];
                    for (int i = 0; i < fn; i++) {
                        rowRequire[i] = r_matrix.get((rn + 1) * (i + 1) + 1);
                    }

                    for (int i = 0; i < rn; i++) {
                        columnRequire[i] = r_matrix.get((rn + 1) * fn + i + 2);
                    }

                    for (int row = 0; row < fn; row++) {
                        for (int col = 0; col < rn; col++) {
                            matrix[row][col] = r_matrix.get((rn + 1) * row + col + 2);
                        }
                    }
                    //开始生成
                    int totalD = rowRequire.length + columnRequire.length;
                    int restrict_size = rowRequire.length * columnRequire.length + totalD;
                    int x_size = 1 << totalD;//n
                    double[][] data = new double[restrict_size][x_size];
                    for (int m = 0; m < rowRequire.length; m++) {
                        int mt = 1 << m;
                        for (int n = 0; n < columnRequire.length; n++) {
                            int nt = 1 << n;
                            for (int mm = 0; mm < (1 << rowRequire.length); mm++) {
                                for (int nn = 0; nn < (1 << columnRequire.length); nn++) {
                                    if ((mm & mt) != 0 && (nn & nt) != 0) {
                                        System.out.println("row:" + (m * columnRequire.length + n));
                                        System.out.println("column:" + ((mm << columnRequire.length) + nn));
                                        data[m * columnRequire.length + n][(mm << columnRequire.length) + nn] = 1;
                                    }
                                }
                            }
                        }
                    }

                    for (int m = 0; m < rowRequire.length; m++) {
                        int mt = 1 << m;
                        for (int mm = 0; mm < (1 << rowRequire.length); mm++) {
                            if ((mm & mt) != 0) {
                                for (int nn = 0; nn < (1 << columnRequire.length); nn++) {
                                    data[rowRequire.length * columnRequire.length + m][(mm << columnRequire.length) + nn] = 1;
                                }
                            }
                        }
                    }

                    for (int n = 0; n < columnRequire.length; n++) {
                        int nt = 1 << n;
                        for (int nn = 0; nn < (1 << columnRequire.length); nn++) {
                            if ((nn & nt) != 0) {
                                for (int mm = 0; mm < (1 << rowRequire.length); mm++) {
                                    data[rowRequire.length * columnRequire.length + rowRequire.length + n][(mm << columnRequire.length) + nn] = 1;
                                }
                            }
                        }
                    }

                    System.out.println("计算前的A:");
                    System.out.println(Arrays.deepToString(data));

                    double[] b_list = new double[restrict_size];
                    for (int m = 0; m < rowRequire.length; m++) {
                        for (int n = 0; n < columnRequire.length; n++) {
                            b_list[m * columnRequire.length + n] = matrix[m][n];
                        }
                    }
                    for (int m = 0; m < rowRequire.length; m++) {
                        b_list[rowRequire.length * columnRequire.length + m] = rowRequire[m];
                    }
                    for (int n = 0; n < columnRequire.length; n++) {
                        b_list[rowRequire.length * columnRequire.length + rowRequire.length + n] = columnRequire[n];
                    }

                    System.out.println("计算前的B:");
                    System.out.println(Arrays.toString(b_list));

                    LinearEquationSolver solver = new LinearEquationSolver(restrict_size, x_size, MatrixUtils.createRealMatrix(data), MatrixUtils.createRealVector(b_list));
                    solver.standardize();
                    System.out.println(solver);

                    RealVector result = solver.squarize();
                    double sumAll = Arrays.stream(result.toArray()).sum();
                    result = result.map(x -> Math.round(x / sumAll * 1000));

                    System.out.println("初始分布：");
                    System.out.println(result);

                    rowMap.put(id, fn);
                    columnMap.put(id, rn);
                    trainMap.put(id, result);

                    return JSONArray.fromObject(result.toArray());
                }
                return new JSONArray();
            });
        }
    }

    public static void main(String[] args) {

    }

    public static double[][] feat(int rowCount, int columnCount, RealVector dist, int feature) {
        double[][] props = new double[columnCount][3];
        int[][] total_ps = new int[columnCount][3];
        for (int n = 0; n < columnCount; n++) {
            int eg_count = 0, eg_rf = 0;
            int em_count = 0, em_rf = 0;
            int er_count = 0, er_rf = 0;
            for (int i = 0; i < (1 << (rowCount + columnCount)); i++) {
                int sub_f = (i >> columnCount);
                System.out.println("subf=" + sub_f + "\t,\t feature=" + feature);
                if (sub_f == feature) {
                    eg_count += dist.getEntry(i);
                    if ((i & (1 << n)) > 0) {
                        eg_rf += dist.getEntry(i);
                    }
                }

                if ((sub_f & feature) == sub_f) {
                    em_count += dist.getEntry(i);
                    if ((i & (1 << n)) > 0) {
                        em_rf += dist.getEntry(i);
                    }
                }

                for (int row = 0; row < rowCount; row++) {
                    if (((1 << row) & sub_f) == ((1 << row) & (sub_f & feature))) {
                        er_count += dist.getEntry(i);
                        if ((i & (1 << n)) > 0) {
                            er_rf += dist.getEntry(i);
                        }
                        break;
                    }
                }
            }
            total_ps[n][0] = eg_count;
            total_ps[n][1] = em_count;
            total_ps[n][2] = er_count;

            props[n][0] = eg_rf * 1.0 / eg_count;
            props[n][1] = em_rf * 1.0 / em_count;
            props[n][2] = er_rf * 1.0 / er_count;

            System.out.printf("%d\t%.2f\t%.2f\t%.2f\n", n + 1, props[n][0], props[n][1], props[n][2]);
//                    System.out.printf("%d\t%.2f\n",n+1,eg_count*0.001);
        }
        System.out.println(Arrays.deepToString(total_ps));
        System.out.println(Arrays.deepToString(props));
        return props;
    }

    public static RealVector retrain(int rowCount, int columnCount, RealVector dist, int feature, int reasonToSet) {
        double[][] props = new double[columnCount][3];
        int[][] total_ps = new int[columnCount][3];
        for (int n = 0; n < columnCount; n++) {
            int eg_count = 0, eg_rf = 0;
            int em_count = 0, em_rf = 0;
            int er_count = 0, er_rf = 0;
            for (int i = 0; i < (1 << (rowCount + columnCount)); i++) {
                int sub_f = (i >> columnCount);
                if (sub_f == feature) {
                    eg_count += dist.getEntry(i);
                    if ((i & (1 << n)) > 0) {
                        eg_rf += dist.getEntry(i);
                    }
                }

                if ((sub_f & feature) == sub_f) {
                    em_count += dist.getEntry(i);
                    if ((i & (1 << n)) > 0) {
                        em_rf += dist.getEntry(i);
                    }
                }

                for (int row = 0; row < rowCount; row++) {
                    if (((1 << row) & sub_f) == ((1 << row) & (sub_f & feature))) {
                        er_count += dist.getEntry(i);
                        if ((i & (1 << n)) > 0) {
                            er_rf += dist.getEntry(i);
                        }
                        break;
                    }
                }
            }
            total_ps[n][0] = eg_count;
            total_ps[n][1] = em_count;
            total_ps[n][2] = er_count;

            props[n][0] = eg_rf * 1.0 / eg_count;
            props[n][1] = em_rf * 1.0 / em_count;
            props[n][2] = er_rf * 1.0 / er_count;


            System.out.printf("%d\t%.2f\t%.2f\t%.2f\n", n + 1, props[n][0], props[n][1], props[n][2]);
//                    System.out.printf("%d\t%.2f\n",n+1,eg_count*0.001);
        }
        System.out.println(Arrays.deepToString(total_ps));
        System.out.println(Arrays.deepToString(props));

        double max_trans = 0;
        for (int n = 0; n < columnCount; n++) {
            //暂只以第二种策略进行调整：
//            System.out.printf("pp[n]=%d,prop=%.2f\n",pp[n],props[n][1]);
            if ((reasonToSet & (1 << n)) > 0 && props[n][1] > 0.5) {
                System.out.println("原因" + (n + 1) + "匹配");
            } else if ((reasonToSet & (1 << n)) == 0 && props[n][1] < 0.5) {
                System.out.println("原因" + (n + 1) + "匹配");
            } else {
                System.out.println("原因" + (n + 1) + "不匹配，待调整。");
                max_trans = Math.max(Math.max(Math.abs(props[n][1] - 0.5), 0.01), max_trans);
                max_trans = Math.min(max_trans * (1000.0 / total_ps[n][1] - 1), 0.1);
            }
        }

        //进行调整：
        int toModifyCount = (int) (1000 * max_trans);
        System.out.println("调整幅度：" + toModifyCount);
        Random random = new Random();
        int x_size = (1 << (rowCount + columnCount)) - (1 << columnCount);
        int step = random.nextInt(x_size);
        for (int i = 0; i < toModifyCount; i++) {
            if (dist.getEntry(step + (1 << columnCount)) > 0) {
                dist.setEntry(step + (1 << columnCount), dist.getEntry(step + (1 << columnCount)) - 1.0);
                step = random.nextInt(x_size);
            } else {
                i--;
                step++;
                step = step % x_size;
            }
        }
        int index = feature << columnCount;
        index += reasonToSet;
        System.out.println("加强：" + index);

//      System.out.printf("%b\n",);
        dist.setEntry(index, dist.getEntry(index) + toModifyCount);

        System.out.println("调整完毕！调整后分布为：");
        System.out.println(dist);

        return dist;
    }
}
